home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Nebula 2
/
Nebula Two.iso
/
SourceCode
/
Palettes
/
Chart
/
Source
/
BarChart.m
< prev
next >
Wrap
Text File
|
1995-06-12
|
35KB
|
1,417 lines
// -------------------------------------------------------------------------------------
// BarChart.m
// Martin D. Flynn, NeXT Computer, Inc.
// -------------------------------------------------------------------------------------
#import <appkit/appkit.h>
#import <libc.h>
#import <c.h>
#import <stdlib.h>
#import <string.h>
#import <math.h>
#import <dpsclient/wraps.h>
#import <streams/streams.h>
#import <objc/Storage.h>
#import <objc/List.h>
#import "BarChartPS.h"
#import "BarChart.h"
// -------------------------------------------------------------------------------------
// archive version number
#define VERSION 2
// -------------------------------------------------------------------------------------
// data source types
#define srcTypeSAMPLE -1
#define srcTypeNONE 0
#define srcTypeTEXT 1
#define srcTypeMATRIX 2
#define srcTypeSTORAGE 3
#define srcTypeSTREAM 4
// -------------------------------------------------------------------------------------
#define X origin.x
#define Y origin.y
#define W size.width
#define H size.height
#define fontWIDTH(L) [currentFont getWidthOf:L]
// -------------------------------------------------------------------------------------
// x/y plot/scale conversions
#define xPOINT(P) (cFlags.xLogScale? [self log:(P)] : (P))
#define yPOINT(P) (cFlags.yLogScale? [self log:(P)] : (P))
#define xSCALE(P) (dataScale.x * xPOINT(P))
#define ySCALE(P) (dataScale.y * yPOINT(P))
#define xPLOT(P) (axisOrigin.x + xSCALE(P))
#define yPLOT(P) (axisOrigin.y + ySCALE(P))
// -------------------------------------------------------------------------------------
@implementation BarChart
// -------------------------------------------------------------------------------------
// set archive version number
static BOOL _nibMode = NO;
+ initialize
{
[self setVersion:VERSION];
if (!strcmp([NXApp appName], "InterfaceBuilder")) _nibMode = YES;
return self;
}
// -------------------------------------------------------------------------------------
// return inspector name
- (const char*)getInspectorClassName { return "BarChartInspector"; }
// -------------------------------------------------------------------------------------
// color support
/* convert color to gray */
static float _colorGray(NXColor color)
{
float gray;
NXConvertColorToGray(color, &gray);
return gray;
}
/* set color/gray */
static void _setColor(NXColor color, BOOL isColor)
{
if (isColor) NXSetColor(color); else PSsetgray(_colorGray(color));
}
// -------------------------------------------------------------------------------------
// new
- initFrame:(const NXRect*)r
{
/* instantiate view */
self = [super initFrame:r];
isFirstResponder = NO;
/* init font */
currentFont = [Font newFont:"Helvetica" size:10.0 matrix:NX_IDENTITYMATRIX];
/* initialize flags */
memset(&cFlags, 0, sizeof(cFlags));
cFlags.xShowLabels = YES;
cFlags.xLogScale = NO;
cFlags.xGridDraw = NO;
cFlags.yShowLabels = YES;
cFlags.yLogScale = NO;
cFlags.yGridDraw = YES;
cFlags.drawInColor = NO;
/* initialize colors */
backgroundColor = NXConvertGrayToColor(NX_LTGRAY);
axisColor = NXConvertGrayToColor(NX_BLACK);
yGridColor = NXConvertGrayToColor(NX_WHITE);
xGridColor = NXConvertGrayToColor(NX_WHITE);
/* default chart (for IB use) */
dftChartType = chartBAR;
dftChartColor = NXConvertGrayToColor(NX_DKGRAY);
/* init outlets */
delegate = self;
xLabelMatrix = (id)nil;
yLabelMatrix = (id)nil;
actionMatrix = (id)nil;
/* init defaut chart */
dataList = (id)nil;
dataRange.X = 0.0;
dataRange.Y = 0.0;
dataRange.W = 10.0;
dataRange.H = 5.0;
tickMark.X = 0.0;
tickMark.Y = 0.0;
tickMark.W = 2.0;
tickMark.H = 1.0;
/* flag for rescaling */
reScale = YES;
return self;
}
/* free */
- free
{
if (dataList) [dataList free];
return [super free];
}
// -------------------------------------------------------------------------------------
// access to internal charting data structure list
// -------------------------------------------------------------------------------------
/* return charting data list */
- (Storage*)chartList
{
return dataList;
}
/* return number of chart types in list */
- (int)chartCount
{
return [dataList count];
}
/* return pointer to specific chart item */
- (chartData_s*)chartDataAt:(int)index
{
return (chartData_s*)[dataList elementAt:index];
}
// -------------------------------------------------------------------------------------
// log conversion method
// -------------------------------------------------------------------------------------
/* return log converted data point */
- (float)log:(float)num
{
return num? (float)log10(fabs((double)num)) : 0.0;
}
/* return antiLog converted data point */
- (float)exp:(float)num
{
return (float)pow((double)10.0, (double)num);
}
// -------------------------------------------------------------------------------------
// chart data point conversion
// -------------------------------------------------------------------------------------
/* convert data point to chart */
- convertDataPointToChart:(NXPoint*)point
{
point->x = xPLOT(point->x);
point->y = xPLOT(point->y);
return self;
}
/* convert chart point to data */
- convertChartPointToData:(NXPoint*)point
{
NXPoint p;
p.x = (point->x - axisOrigin.x) / dataScale.x;
p.y = (point->y - axisOrigin.y) / dataScale.y;
point->x = cFlags.xLogScale? [self exp:p.x] : p.x;
point->y = cFlags.yLogScale? [self exp:p.y] : p.y;
return self;
}
// -------------------------------------------------------------------------------------
// set plot options
// -------------------------------------------------------------------------------------
/* set delegate */
- setDelegate:anObject
{
delegate = anObject;
return self;
}
/* return current delegate */
- delegate
{
return delegate;
}
/* set font (make a new unflipped font copy) */
// This is done this way on purpose
- setFont:fontId
{
currentFont = [Font newFont:[fontId name] size:[fontId pointSize] matrix:NX_IDENTITYMATRIX];
reScale = YES;
return self;
}
/* return current font (NX_FLIPPEDMATRIX) */
// This is done this way on purpose (IB can't handle it properly any other way)
- font
{
return [Font newFont:[currentFont name] size:[currentFont pointSize]];
}
/* return isColor status */
- (BOOL)drawInColor
{
return cFlags.drawInColor;
}
/* set background transparent */
- setBackgroundTransparent:(BOOL)flag
{
cFlags.transparent = flag;
return self;
}
/* return background transparent state */
- (BOOL)backgroundTransparent
{
return cFlags.transparent;
}
/* set background gray */
- setBackgroundGray:(float)gray
{
backgroundColor = NXConvertGrayToColor(gray);
cFlags.drawInColor = NO;
return self;
}
/* return background gray */
- (float)backgroundGray
{
return _colorGray(backgroundColor);
}
/* set background color */
- setBackgroundColor:(NXColor)color
{
backgroundColor = color;
cFlags.drawInColor = YES;
return self;
}
/* return background color */
- (NXColor)backgroundColor
{
return backgroundColor;
}
/* set default chart gray */
- setDftChartGray:(float)gray
{
dftChartColor = NXConvertGrayToColor(gray);
cFlags.drawInColor = NO;
return self;
}
/* return default chart gray */
- (float)dftChartGray
{
return _colorGray(dftChartColor);
}
/* set default chart gray */
- setDftChartColor:(NXColor)color
{
dftChartColor = color;
cFlags.drawInColor = YES;
return self;
}
/* return default chart color */
- (NXColor)dftChartColor
{
return dftChartColor;
}
/* set axis gray */
- setAxisGray:(float)gray
{
axisColor = NXConvertGrayToColor(gray);
cFlags.drawInColor = NO;
return self;
}
/* return axis gray */
- (float)axisGray
{
return _colorGray(axisColor);
}
/* set axis color */
- setAxisColor:(NXColor)color
{
axisColor = color;
cFlags.drawInColor = YES;
return self;
}
/* return axis color */
- (NXColor)axisColor
{
return axisColor;
}
/* set to draw x grid */
- setXGridDraw:(BOOL)flag
{
cFlags.xGridDraw = flag;
return self;
}
/* return x grid drawing status */
- (BOOL)xGridDraw
{
return cFlags.xGridDraw;
}
/* set x grid gray */
- setXGridGray:(float)gray
{
xGridColor = NXConvertGrayToColor(gray);
cFlags.drawInColor = NO;
return self;
}
/* return x grid gray */
- (float)xGridGray
{
return _colorGray(xGridColor);
}
/* set x grid color */
- setXGridColor:(NXColor)color
{
xGridColor = color;
cFlags.drawInColor = YES;
return self;
}
/* return x grid color */
- (NXColor)xGridColor
{
return xGridColor;
}
/* set to draw y grid */
- setYGridDraw:(BOOL)flag
{
cFlags.yGridDraw = flag;
return self;
}
/* return y grid drawing status */
- (BOOL)yGridDraw
{
return cFlags.yGridDraw;
}
/* set y grid gray */
- setYGridGray:(float)gray
{
yGridColor = NXConvertGrayToColor(gray);
cFlags.drawInColor = NO;
return self;
}
/* return y grid gray */
- (float)yGridGray
{
return _colorGray(yGridColor);
}
/* set y grid color */
- setYGridColor:(NXColor)color
{
yGridColor = color;
cFlags.drawInColor = YES;
return self;
}
/* return y grid color */
- (NXColor)yGridColor
{
return yGridColor;
}
/* set default chart type */
- setDftChartType:(int)chartType
{
dftChartType = chartType;
return self;
}
/* return default chart type */
- (int)dftChartType
{
return dftChartType;
}
/* data range */
- setDataRange:(NXRect*)range
{
dataRange = *range;
reScale = YES;
return self;
}
/* return data range */
- (NXRect*)dataRange
{
return &dataRange;
}
/* tick marks (origin currently not used) */
- setTickMark:(NXRect*)tick
{
tickMark = *tick;
tickMark.X = tickMark.Y = 0.0;
reScale = YES;
return self;
}
/* return tick mark settings */
- (NXRect*)tickMark
{
return &tickMark;
}
/* set x log scaling */
- setXLogScaling:(BOOL)flag
{
cFlags.xLogScale = flag;
reScale = YES;
return self;
}
/* return x log scaling */
- (BOOL)xLogScaling
{
return cFlags.xLogScale;
}
/* set y log scaling */
- setYLogScaling:(BOOL)flag
{
cFlags.yLogScale = flag;
reScale = YES;
return self;
}
/* return y log scaling */
- (BOOL)yLogScaling
{
return cFlags.yLogScale;
}
/* set x show labels */
- setXShowLabels:(BOOL)flag
{
cFlags.xShowLabels = flag;
reScale = YES;
return self;
}
/* return x show labels */
- (BOOL)xShowLabels
{
return cFlags.xShowLabels;
}
/* set y show labels */
- setYShowLabels:(BOOL)flag
{
cFlags.yShowLabels = flag;
reScale = YES;
return self;
}
/* return y show labels */
- (BOOL)yShowLabels
{
return cFlags.yShowLabels;
}
/* rotate x axis labels */
- setXLabelRotate:(BOOL)flag
{
cFlags.xLabelRotate = flag;
reScale = YES;
return self;
}
/* return X axis label rotate state */
- (BOOL)xLabelRotate
{
return cFlags.xLabelRotate;
}
/* rotate y axis labels */
- setYLabelRotate:(BOOL)flag
{
cFlags.yLabelRotate = flag;
reScale = YES;
return self;
}
/* return Y axis label rotate state */
- (BOOL)yLabelRotate
{
return cFlags.yLabelRotate;
}
// -------------------------------------------------------------------------------------
// actions
// -------------------------------------------------------------------------------------
/* redisplay chart from source */
- update:sender
{
return [self display];
}
/* override from super to rescale if frame changes */
- sizeTo:(NXCoord)w :(NXCoord)h
{
reScale = YES;
return [super sizeTo:w :h];
}
/* add data source */
- (int)_addData:srcId:(int)sType chart:(int)cType:(float)width:(NXColor)color:(BOOL)isColor
{
int count;
chartData_s src = { sType, srcId, cType, NO, width, isColor, color };
if (sType == srcTypeNONE) return -1;
if (!dataList) dataList = [[[Storage alloc] initCount:1 elementSize:sizeof(chartData_s)
description:(char*)nil] empty];
count = [dataList count];
[dataList addElement:&src];
return count;
}
/* return source type */
- (int)_srcType:sourceId
{
if ([sourceId isKindOf:[Text class]]) return srcTypeTEXT;
if ([sourceId isKindOf:[Matrix class]]) return srcTypeMATRIX;
if ([sourceId isKindOf:[Storage class]]) return srcTypeSTORAGE;
return srcTypeNONE;
}
/* add data source (returns list index) (gray) */
- (int)addDataSource:sourceId type:(int)type lineWidth:(float)width gray:(float)gray
{
NXColor color = NXConvertGrayToColor(gray);
id src = ([sourceId isKindOf:[ScrollView class]])? [sourceId docView] : sourceId;
return [self _addData:src:[self _srcType:src] chart:type:width:color:NO];
}
/* add data source (returns list index) (color) */
- (int)addDataSource:sourceId type:(int)type lineWidth:(float)width color:(NXColor)color
{
id src = ([sourceId isKindOf:[ScrollView class]])? [sourceId docView] : sourceId;
return [self _addData:src:[self _srcType:src] chart:type:width:color:YES];
}
/* add data stream (gray) */
- (int)addDataStream:(NXStream*)s type:(int)type lineWidth:(float)width gray:(float)gray
{
NXColor color = NXConvertGrayToColor(gray);
return [self _addData:(id)s:srcTypeSTREAM chart:type:width:color:NO];
}
/* add data stream (color) */
- (int)addDataStream:(NXStream*)s type:(int)type lineWidth:(float)width color:(NXColor)color
{
return [self _addData:(id)s:srcTypeSTREAM chart:type:width:color:YES];
}
/* default data source outlet (if added, index will always be zero) */
- setDataSource:anObject
{
int n;
NXColor color = dftChartColor;
if (dataList) { [dataList free]; dataList = (id)nil; }
if (!anObject) return self;
n = cFlags.drawInColor?
[self addDataSource:anObject type:dftChartType lineWidth:-1.0 color:color]:
[self addDataSource:anObject type:dftChartType lineWidth:-1.0 gray:_colorGray(color)];
if (n >= 0) [self chartDataAt:n]->isEditable = YES;
return self;
}
/* set x label matrix */
- setXLabelMatrix:anObject
{
if (![anObject isKindOf:[Matrix class]]) return (id)nil;
xLabelMatrix = anObject;
reScale = YES;
return self;
}
/* return current x-axis label matrix */
- xLabelMatrix
{
return xLabelMatrix;
}
/* set y label matrix */
- setYLabelMatrix:anObject
{
if (![anObject isKindOf:[Matrix class]]) return (id)nil;
yLabelMatrix = anObject;
reScale = YES;
return self;
}
/* return current y-axis label matrix */
- yLabelMatrix
{
return yLabelMatrix;
}
// -------------------------------------------------------------------------------------
// data retrieval methods
// -------------------------------------------------------------------------------------
/* return number of points in data chart set */
- (int)_pointCount:(chartData_s*)dtaPtr
{
if (!dtaPtr) return MIN((floor(dataRange.W) - 1.0), 30.0); // inadequate for xLogScale?
switch (dtaPtr->_srcType) {
case srcTypeMATRIX: return [[dtaPtr->_srcId cellList] count];
case srcTypeSTORAGE: return [dtaPtr->_srcId count];
}
return 0;
}
/* return specific point */
#define RND(S) (srandom(S*21),(float)(random()&0xFFFF)/(float)0xFFFF)
- _dataItemAt:(int)index :(chartData_s*)dtaPtr :(float*)x:(float*)y
{
id cellId;
NXPoint *pt;
/* check for sample default chart type */
if (!dtaPtr) {
float gx, gy;
if (!_nibMode) return (id)nil;
gx = dataRange.X+1.0+(float)(index*(cFlags.xLogScale?rint(dataRange.X+10.0/10.0):1));
gy = dataRange.Y+dataRange.H*(cFlags.yLogScale?0.15:0.3)*(1.0+RND(index));
*x = xPLOT(gx);
*y = yPLOT(gy);
return self;
}
/* normal chart type */
switch (dtaPtr->_srcType) {
case srcTypeMATRIX: // Matrix
if (!(cellId = [[dtaPtr->_srcId cellList] objectAt:index])) return (id)nil;
if (![cellId respondsTo:@selector(floatValue)]) return (id)nil;
*y = yPLOT([cellId floatValue]);
*x = xPLOT((float)[cellId tag]);
return self;
case srcTypeSTORAGE: // Storage
pt = (NXPoint*)[dtaPtr->_srcId elementAt:index];
*x = xPLOT(pt->x);
*y = yPLOT(pt->y);
return self;
}
return (id)nil;
}
/* build storage object containing points fount in stream (MUST BE EXPLICITLY FREED) */
- _parseStream:(NXStream*)s :(BOOL)all
{
int chartType = dftChartType;
NXColor chartColor;
BOOL chartIsColor = NO, newChart;
chartData_s dtaSrc;
id dList = (id)nil;
/* reset to beginning */
NXSeek(s, 0L, NX_FROMSTART);
/* read stream */
for (newChart = YES; !NXAtEOS(s); ) {
int v;
NXPoint pt;
char *p, b[256];
/* read line */
for (p = b; !NXAtEOS(s); p++) {
*p = (char)NXGetc(s);
if (!*p || (*p == '\n')) break;
} *p = 0;
/* check for command */
if ((*b == ':') && !strncasecmp(b, ":chart ", 7)) {
char typeStr[256];
float red = -1.0, green = -1.0, blue = -1.0;
int n, rtn;
/* default chart */
newChart = YES;
chartType = dftChartType;
chartColor = dftChartColor;
chartIsColor = cFlags.drawInColor;
if (sscanf(b, "%*s %s %n", typeStr, &n) != 1) continue;
/* change chart type */
chartType = 0;
if (index(typeStr, '*')) chartType = dftChartType;
if (index(typeStr, 'b')) chartType |= chartBAR;
if (index(typeStr, 'l')) chartType |= chartLINE;
if (index(typeStr, 'a')) chartType |= chartAREA;
if (index(typeStr, 'p')) chartType |= chartPOINT;
if (n > strlen(b)) continue;
/* change color by name */
if (b[n] == '*') {
char c[128];
sscanf(&b[n+1], "%s", c);
if (!strcasecmp(c,"black" )) {chartColor=NX_COLORBLACK ; chartIsColor=NO ;} else
if (!strcasecmp(c,"dkgray" )) {chartColor=NX_COLORDKGRAY ; chartIsColor=NO ;} else
if (!strcasecmp(c,"ltgray" )) {chartColor=NX_COLORLTGRAY ; chartIsColor=NO ;} else
if (!strcasecmp(c,"white" )) {chartColor=NX_COLORWHITE ; chartIsColor=NO ;} else
if (!strcasecmp(c,"gray" )) {chartColor=NX_COLORGRAY ; chartIsColor=YES;} else
if (!strcasecmp(c,"red" )) {chartColor=NX_COLORRED ; chartIsColor=YES;} else
if (!strcasecmp(c,"orange" )) {chartColor=NX_COLORORANGE ; chartIsColor=YES;} else
if (!strcasecmp(c,"yellow" )) {chartColor=NX_COLORYELLOW ; chartIsColor=YES;} else
if (!strcasecmp(c,"green" )) {chartColor=NX_COLORGREEN ; chartIsColor=YES;} else
if (!strcasecmp(c,"blue" )) {chartColor=NX_COLORBLUE ; chartIsColor=YES;} else
if (!strcasecmp(c,"purple" )) {chartColor=NX_COLORPURPLE ; chartIsColor=YES;} else
if (!strcasecmp(c,"cyan" )) {chartColor=NX_COLORCYAN ; chartIsColor=YES;} else
if (!strcasecmp(c,"brown" )) {chartColor=NX_COLORBROWN ; chartIsColor=YES;} else
if (!strcasecmp(c,"magenta")) {chartColor=NX_COLORMAGENTA; chartIsColor=YES;} else
if (!strcasecmp(c,"clear" )) {chartColor=NX_COLORCLEAR ; chartIsColor=YES;}
continue;
}
/* change color by number */
rtn = sscanf(&b[n], "%f %f %f", &red, &green, &blue);
chartIsColor = (rtn == 3)? YES : NO;
if (chartIsColor) chartColor = NXConvertRGBToColor(red, green, blue);
else chartColor = NXConvertGrayToColor(red);
continue;
}
/* new chart */
if (newChart) {
if (dList && !all) break; // already have the first set
memset(&dtaSrc, 0, sizeof(chartData_s));
dtaSrc._srcType = srcTypeSTORAGE;
dtaSrc._srcId = [[[Storage alloc] initCount:1 elementSize:sizeof(NXPoint)
description:(char*)nil] empty];
dtaSrc.chartType = chartType;
dtaSrc.isEditable = NO;
dtaSrc.lineWidth = -1.0;
dtaSrc.isColor = chartIsColor;
dtaSrc.chartColor = chartColor;
if (!dList) dList = [[[Storage alloc] initCount:1 elementSize:sizeof(chartData_s)
description:(char*)nil] empty];
[dList addElement:&dtaSrc];
newChart = NO;
}
/* parse data point */
v = sscanf(b, "%f %f ", &pt.x, &pt.y);
if (v <= 0) continue;
if (v == 1) { pt.y = pt.x; pt.x = (float)[dtaSrc._srcId count]; }
[dtaSrc._srcId addElement:&pt];
}
/* return parse data list */
if (dList && ![dList count]) { [dList free]; dList = (id)nil; }
return dList;
}
// -------------------------------------------------------------------------------------
// plot chart
// -------------------------------------------------------------------------------------
/* chart data */
- _chartData:(chartData_s*)dtaPtr
{
BOOL first;
float x, y, lineWidth;
int i, cnt = [self _pointCount:dtaPtr], chartType;
/* check for sample chart type */
if (dtaPtr) {
if (dtaPtr->isColor) NXSetColor(dtaPtr->chartColor);
else PSsetgray(_colorGray(dtaPtr->chartColor));
chartType = dtaPtr->chartType;
lineWidth = dtaPtr->lineWidth;
} else {
_setColor(dftChartColor, cFlags.drawInColor);
chartType = dftChartType;
lineWidth = -1.0;
}
/* area chart */
if (chartType & chartAREA) {
PSsetlinewidth((lineWidth>=0.0)?lineWidth:1.0);
for (first = YES, i = 0; i < cnt ; i++) {
if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
if (first) { PSmoveto(x, axisOrigin.y); first = NO; }
PSlineto(x, y);
}
if (!first) { PSlineto(x, axisOrigin.y); PSclosepath(); PSfill(); }
}
/* bar chart */
if (chartType & chartBAR) {
PSsetlinewidth((lineWidth>=0.0)?xSCALE(lineWidth):xSCALE(0.8));
for (i = 0; i < cnt ; i++) {
if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
_barALine(x, axisOrigin.y, x, y);
}
}
/* line chart */
if (chartType & chartLINE) {
PSsetlinewidth((lineWidth>=0.0)?lineWidth:1.0);
PSnewpath();
for (first = YES, i = 0; i < cnt ; i++) {
if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
if (first) { PSmoveto(x, y); first = NO; }
else PSlineto(x, y);
}
if (!first) PSstroke();
}
/* point chart */
if (chartType & chartPOINT) {
PSsetlinewidth((lineWidth>=0.0)?lineWidth:1.0);
for (i = 0; i < cnt ; i++) {
if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
_barDrawPoint(x, y, 3.0);
}
}
return self;
}
// -------------------------------------------------------------------------------------
// draw chart view
// -------------------------------------------------------------------------------------
/* calculate printed decimal precision */
static int _dPrecision(float num)
{
int p;
char floatStr[32], *fPtr;
if (num > 9999.0) return 0;
sprintf(floatStr, "%f", num);
for (fPtr = floatStr+strlen(floatStr)-1; (*fPtr=='0') || (*fPtr==' ');) *(fPtr--) = 0;
for (fPtr = floatStr; *fPtr && (*fPtr != '.'); fPtr++);
p = (*fPtr)? strlen(fPtr + 1) : 0;
return p;
}
/* rescale axis */
- _rescaleAxis
{
char label[64];
NXSize xLS, yLS;
float fontHeight = [currentFont pointSize] * 0.76;
/* save upper range limit */
maxRange.x = dataRange.X + dataRange.W;
maxRange.y = dataRange.Y + dataRange.H;
/* check number of tick marks */
if (!cFlags.xLogScale && (dataRange.W / tickMark.W >= 250.0))
tickMark.W = dataRange.W / 250.0;
if (!cFlags.yLogScale && (dataRange.H / tickMark.H >= 250.0))
tickMark.H = dataRange.H / 250.0;
/* calc tick mark length and line width */
tickLength = MAX(MIN(fontHeight, 12.0), 5.0);
tickWidth = ([currentFont pointSize] >= 20.0)? 2.0 : 1.0;
/* label precision */
precisionX = _dPrecision(tickMark.W);
precisionY = _dPrecision(tickMark.H);
/* label size */
if (xLabelMatrix) [xLabelMatrix getCellSize:&xLblSize];
else {
sprintf(label, "%.*f", precisionX, maxRange.x);
xLblSize.width = fontWIDTH(label);
xLblSize.height = fontHeight;
}
if (!cFlags.xLabelRotate) xLS = xLblSize;
else { xLS.width = xLblSize.height; xLS.height = xLblSize.width; }
if (yLabelMatrix) [yLabelMatrix getCellSize:&yLblSize];
else {
sprintf(label, "%.*f", precisionY, maxRange.y);
yLblSize.width = fontWIDTH(label);
yLblSize.height = fontHeight;
}
if (!cFlags.yLabelRotate) yLS = yLblSize;
else { yLS.width = yLblSize.height; yLS.height = yLblSize.width; }
/* calculate chart frame */
chartFrame.X = (cFlags.yShowLabels?yLS.width :0.0) + tickLength / 2.0 + 2.0;
if (chartFrame.X < xLS.width / 2.0) chartFrame.X = xLS.width / 2.0;
chartFrame.Y = (cFlags.xShowLabels?xLS.height:0.0) + tickLength / 2.0 + 2.0;
chartFrame.W = bounds.W - chartFrame.X - (cFlags.xShowLabels?xLS.width *0.50:2.0);
chartFrame.H = bounds.H - chartFrame.Y - (cFlags.yShowLabels?yLS.height :2.0);
/* scale factor */
dataScale.x = dataRange.W? chartFrame.W/(xPOINT(maxRange.x)-xPOINT(dataRange.X)) : 0.0;
dataScale.y = dataRange.H? chartFrame.H/(yPOINT(maxRange.y)-yPOINT(dataRange.Y)) : 0.0;
/* axis origin */
axisOrigin.x = chartFrame.X - xSCALE(dataRange.X);
axisOrigin.y = chartFrame.Y - ySCALE(dataRange.Y);
/* actual displayed axis position */
axisPosition.x = xPLOT((maxRange.x > 0.0)? MAX(dataRange.X, 0.0): MIN(maxRange.x, 0.0));
axisPosition.y = yPLOT((maxRange.y > 0.0)? MAX(dataRange.Y, 0.0): MIN(maxRange.y, 0.0));
return self;
}
/* draw Y grid lines (parallel X-axis) */
- _drawYGridLines
{
if (cFlags.yGridDraw && (tickMark.H > 0.0)) {
float t = tickMark.H, y = t * (long)(dataRange.Y / t), p;
if (y < dataRange.Y) y += t;
_setColor(yGridColor, cFlags.drawInColor);
PSsetlinewidth(tickWidth);
for (p = y; p <= maxRange.y; p += t) {
_barRLine(chartFrame.X, yPLOT(p), chartFrame.W, 0.0);
if (cFlags.yLogScale) while (p / t >= 10.0) t *= 10.0;
}
}
return self;
}
/* draw X grid lines (parallel Y-axis) */
- _drawXGridLines
{
if (cFlags.xGridDraw && (tickMark.W > 0.0)) {
float t = tickMark.W, x = t * (long)(dataRange.X / t), p;
if (x < dataRange.X) x += t;
_setColor(xGridColor, cFlags.drawInColor);
PSsetlinewidth(tickWidth);
for (p = x; p <= maxRange.x; p += t) {
_barRLine(xPLOT(p), chartFrame.Y, 0.0, chartFrame.H);
if (cFlags.xLogScale) while (p / t >= 10.0) t *= 10.0;
}
}
return self;
}
/* draw Y axis */
- _drawYAxis
{
PSsetlinewidth(tickWidth);
_setColor(axisColor, cFlags.drawInColor);
_barRLine(axisPosition.x, chartFrame.Y, 0.0, chartFrame.H);
return self;
}
/* draw X axis */
- _drawXAxis
{
PSsetlinewidth(tickWidth);
_setColor(axisColor, cFlags.drawInColor);
_barRLine(chartFrame.X, axisPosition.y, chartFrame.W, 0.0);
return self;
}
/* draw Y-axis label */
- _drawYLabel:(int)i:(float)p:(NXPoint*)point
{
char label[64];
NXRect r;
BOOL stateSaved = NO;
/* label size */
if (yLabelMatrix) {
*label = 0;
r.W = yLblSize.width;
r.H = yLblSize.height;
} else {
sprintf(label, "%.*f", precisionY, p);
r.W = fontWIDTH(label);
r.H = yLblSize.height / 4.0;
}
/* label orientation */
if (cFlags.yLabelRotate) {
PSgsave(); stateSaved = YES;
PStranslate(point->x - 1.0, MAX(point->y - r.H / 2.0, 0.0));
PSrotate(90.0);
r.X = 0.0;
r.Y = 0.0;
} else {
r.X = point->x - r.W - 1.0;
r.Y = MAX(point->y - r.H / 2.0, 0.0);
}
/* draw label */
if (yLabelMatrix) {
id cellId = [yLabelMatrix cellAt:i:0];
if (!cellId) cellId = [yLabelMatrix cellAt:0:i];
if (cellId) [cellId drawSelf:&r inView:self];
} else _barDrawText(r.X, r.Y, label);
/* restore state */
if (stateSaved) PSgrestore();
return self;
}
/* draw Y-axis labels */
- _drawYAxisLabels
{
if (tickMark.H > 0.0) {
NXPoint point;
int i;
float t = tickMark.H, y = t * (long)(dataRange.Y / t), p;
point.x = axisPosition.x - floor(tickLength / 2.0);
if (y < dataRange.Y) y += t;
if (!yLabelMatrix && cFlags.yShowLabels) [currentFont set];
for (i = 0, p = y; p <= maxRange.y; p += t) {
/* draw tickMark */
point.y = yPLOT(p);
_setColor(axisColor, cFlags.drawInColor);
PSsetlinewidth(tickWidth);
_barRLine(point.x + 1.0, point.y, tickLength, 0.0);
if (!cFlags.yShowLabels) {
if (cFlags.yLogScale) while (p / t >= 10.0) t *= 10.0;
continue;
}
/* draw label */
if (!cFlags.yLogScale) {
[self _drawYLabel:i++:p:&point];
} else
if (p / t >= 10.0) {
[self _drawYLabel:i++:p:&point];
while (p / t >= 10.0) t *= 10.0;
}
}
}
return self;
}
/* draw X-axis label */
- _drawXLabel:(int)i:(float)p:(NXPoint*)point
{
char label[64];
NXRect r;
BOOL stateSaved = NO;
/* label size */
if (xLabelMatrix) {
*label = 0;
r.W = xLblSize.width;
r.H = xLblSize.height;
} else {
sprintf(label, "%.*f", precisionX, p);
r.W = fontWIDTH(label);
r.H = xLblSize.height;
}
/* label orientation */
if (cFlags.xLabelRotate) {
PSgsave(); stateSaved = YES;
PStranslate(point->x + r.H / 2.0, MAX(point->y - r.W - 1.0, 0.0));
PSrotate(90.0);
r.X = 0.0;
r.Y = 0.0;
} else {
r.X = point->x - r.W / 2.0;
r.Y = MAX(point->y - r.H - 1.0, 0.0);
}
/* draw label */
if (xLabelMatrix) {
id cellId = [xLabelMatrix cellAt:0:i];
if (!cellId) cellId = [xLabelMatrix cellAt:i:0];
if (cellId) [cellId drawSelf:&r inView:self];
} else _barDrawText(r.X, r.Y, label);
/* restore state */
if (stateSaved) PSgrestore();
return self;
}
/* draw X-axis labels */
- _drawXAxisLabels
{
if (tickMark.W > 0.0) {
NXPoint point;
int i;
float t = tickMark.W, x = t * (long)(dataRange.X / t), p;
point.y = axisPosition.y - floor(tickLength / 2.0);
if (x < dataRange.X) x += t;
if (!xLabelMatrix && cFlags.xShowLabels) [currentFont set];
for (i = 0, p = x; p <= maxRange.x; p += t) {
/* draw tickMark */
point.x = xPLOT(p);
_setColor(axisColor, cFlags.drawInColor);
PSsetlinewidth(tickWidth);
_barRLine(point.x, point.y + 1.0, 0.0, tickLength);
if (!cFlags.xShowLabels) {
if (cFlags.xLogScale) while (p / t >= 10.0) t *= 10.0;
continue;
}
/* draw label */
if (!cFlags.xLogScale) {
[self _drawXLabel:i++:p:&point];
} else
if (p / t >= 10.0) {
[self _drawXLabel:i++:p:&point];
while (p / t >= 10.0) t *= 10.0;
}
}
}
return self;
}
/* draw data */
- _drawChartData
{
PSgsave();
_barClipBox(chartFrame.X, chartFrame.Y, chartFrame.W, chartFrame.H);
if (dataList) {
int s;
for (s = 0; s < [dataList count]; s++) {
chartData_s *dtaPtr = (chartData_s*)[dataList elementAt:s];
if ((dtaPtr->_srcType == srcTypeTEXT) || (dtaPtr->_srcType == srcTypeSTREAM)) {
id list, src = dtaPtr->_srcId;
NXStream *s = (dtaPtr->_srcType==srcTypeTEXT)?[src stream]:(NXStream*)src;
if (list = [self _parseStream:s:YES]) {
int i, cnt = [list count];
for (i = 0; i < cnt; i++) {
chartData_s *dp = (chartData_s*)[list elementAt:i];
dp->lineWidth = dtaPtr->lineWidth;
[self _chartData:dp];
[dp->_srcId free];
}
[list free];
}
} else [self _chartData:dtaPtr];
}
} else [self _chartData:(chartData_s*)nil];
PSgrestore(); // remove clip path
return self;
}
/* draw all chart types */
// this method has been arranged to provide for easy subclassing
- _drawChart
{
[self _drawYGridLines]; // Y axis grid lines
[self _drawXGridLines]; // X axis grid lines
[self _drawChartData]; // chart data
[self _drawYAxis]; // Y axis
[self _drawXAxis]; // X axis
[self _drawYAxisLabels]; // Y axis labels
[self _drawXAxisLabels]; // X axis labels
return self;
}
/* draw self */
- drawSelf:(const NXRect *)r :(int)n
{
/* fill background */
if (!cFlags.transparent) {
_setColor(backgroundColor, cFlags.drawInColor);
NXRectFill(&bounds);
}
/* rescale if necessary */
if (reScale) { [self _rescaleAxis]; reScale = NO; }
/* draw */
[self _drawChart];
/* highlight if first responder */
//if (isFirstResponder) NXHighlightRect(&bounds);
return self;
}
// -------------------------------------------------------------------------------------
// object archiving stuff
- read:(NXTypedStream*)s
{
int ver;
[super read:s];
NXReadTypes(s, "i",&ver);
NXReadRect (s, &dataRange);
NXReadRect (s, &tickMark);
NXReadTypes(s, "@is",¤tFont,&dftChartType,&cFlags);
if (ver <= 1) { float dmy; NXReadTypes(s, "fffff",&dmy,&dmy,&dmy,&dmy,&dmy); }
backgroundColor = NXReadColor(s);
dftChartColor = NXReadColor(s);
axisColor = NXReadColor(s);
xGridColor = NXReadColor(s);
yGridColor = NXReadColor(s);
delegate = self;
xLabelMatrix = (id)nil;
yLabelMatrix = (id)nil;
actionMatrix = (id)nil;
dataList = (id)nil;
reScale = YES;
return self;
}
- write:(NXTypedStream*)s
{
int ver = [[self class] version];
[super write:s];
NXWriteTypes(s, "i",&ver);
NXWriteRect (s, &dataRange);
NXWriteRect (s, &tickMark);
NXWriteTypes(s, "@is",¤tFont,&dftChartType,&cFlags);
NXWriteColor(s, backgroundColor);
NXWriteColor(s, dftChartColor);
NXWriteColor(s, axisColor);
NXWriteColor(s, xGridColor);
NXWriteColor(s, yGridColor);
return self;
}
// -------------------------------------------------------------------------------------
// firstResponder for getting copy/paste commands
/* allow becoming first responder */
- (BOOL)acceptsFirstResponder
{
return YES;
}
/* become first responder */
- becomeFirstResponder
{
isFirstResponder = YES;
[self display];
return self;
}
/* resign first responder */
- resignFirstResponder
{
isFirstResponder = NO;
[self display];
return self;
}
// -------------------------------------------------------------------------------------
// mouseDown hit detection
/* handle point selected */
- _selectedPoint:(chartData_s*)dtaPtr:(NXPoint*)point:(int)index clicks:(int)clicks
{
if (clicks == 2) {
if (delegate && [delegate respondsTo:@selector(chartDataSelected:index:)]) {
[delegate chartDataSelected:dtaPtr index:index];
}
}
return self;
}
/* chart data */
- (BOOL)_hit:(NXPoint*)pt data:(chartData_s*)dtaPtr hit:(NXPoint*)hit:(int*)index
{
NXPoint w, h;
int i, cnt = [self _pointCount:dtaPtr];
/* valid range */
if (dtaPtr->chartType & chartBAR) { w.x = xSCALE(0.8) / 2.0; w.y = 0; }
else { w.x = 3.0; w.y = w.x; }
/* check hit */
for (i = 0; i < cnt; i++) {
if (![self _dataItemAt:i :dtaPtr :&h.x:&h.y]) break;
if ((pt->x >= h.x - w.x) && (pt->x <= h.x + w.x) &&
(((dtaPtr->chartType & chartBAR) && (pt->y <= h.y + 8.0)) ||
((pt->y >= h.y - w.y) && (pt->y <= h.y + w.y)) ) ) {
*index = i;
*hit = h;
return YES;
}
}
/* no hit */
return NO;
}
/* data point hit detection */
- _dataHitDetection:(NXPoint*)pt clicks:(int)clicks
{
NXPoint hPt;
int s, index = -1;
/* ignore if no data points */
if (!dataList) return self;
/*loop through datasets to find hit */
for (s = [dataList count]; s;) {
BOOL hit = NO;
chartData_s *dtaPtr = (chartData_s*)[dataList elementAt:--s];
if ((dtaPtr->_srcType == srcTypeTEXT) || (dtaPtr->_srcType == srcTypeSTREAM)) {
id list, src = dtaPtr->_srcId;
NXStream *s = (dtaPtr->_srcType == srcTypeTEXT)? [src stream] : (NXStream*)src;
if (list = [self _parseStream:s:NO]) {
int i;
hit = [self _hit:pt data:(chartData_s*)[list elementAt:0] hit:&hPt:&index];
for (i=0;i<[list count];i++) [((chartData_s*)[list elementAt:i])->_srcId free];
[list free];
}
} else hit = [self _hit:pt data:dtaPtr hit:&hPt:&index];
if (hit) { [self _selectedPoint:dtaPtr:&hPt:index clicks:clicks]; break; }
}
return self;
}
/* intercept mouse down */
- mouseDown:(NXEvent*)e
{
NXPoint pt = e->location;
[self convertPoint:&pt fromView:(id)nil];
if (![self mouse:&pt inRect:&chartFrame]) return [super mouseDown:e];
if (e->data.mouse.click == 2) [self _dataHitDetection:&pt clicks:2];
return [super mouseDown:e];
}
// -------------------------------------------------------------------------------------
// chart data point change/selection delegate methods
/* data point selected */
- chartDataSelected:(chartData_s*)dtaPtr index:(int)index
{
id c, m = actionMatrix, srcId = dtaPtr->_srcId;
if (!m && [srcId isKindOf:[Matrix class]] && ([srcId target] != self)) m = srcId;
if (m && (c = [[m cellList] objectAt:index])) { [m selectCell:c]; [m sendAction]; }
return self;
}
@end